<?php
namespace duoge\wechat;


use app\models\OpenweixinForm;
use duoge\wechat\crypto\ErrorCode;
use duoge\wechat\crypto\Prpcrypt;
use duoge\wechat\crypto\SHA1;
use duoge\wechat\crypto\XMLParse;
use duoge\wechat\request\ComponentToken;
use duoge\wechat\request\CreatepreauthcodeRequest;
use duoge\wechat\request\TokenRequest;
use Yii;
use yii\base\BaseObject;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;

/**
 * @property string|null $access_token 小程序或公众号接口调用凭证
 */

class WechatClient extends BaseObject {


    public $Token;
    public $EncodingAESKey;
    public $AppID;
    public $AppSecret;
    public $gatewayUrl = 'https://api.weixin.qq.com/cgi-bin/';

    private $readTimeout;
    private $connectTimeout;

    public static function config($options = []) {
        return new static($options);
    }

    public function __construct($options = []){
        if(is_object($options)) {
            $options = (array)$options;
        }
        if(key_exists("AppID",$options)) {
            $model->AppID = $options["AppID"];
        }
        if(key_exists("AppSecret",$options)) {
            $model->AppSecret = $options["AppSecret"];
        }
        if(key_exists("Token",$options)) {
            $model->Token = $options["Token"];
        }

        if(key_exists("EncodingAESKey",$options)) {
            $model->EncodingAESKey = $options["EncodingAESKey"];
        }

        if(key_exists("gatewayUrl",$options)) {
            $model->gatewayUrl = $options["gatewayUrl"];
        }
    }

    /**
     * @param $request
     * @param $component_access_token
     * @param $access_token
     * @return false|mixed
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function execute($request,$component_access_token=null,$access_token = null){

        if(strstr($request->getApiMethodName(),"https://")) {
            $url = $request->getApiMethodName();
        } else {
            $url = $this->gatewayUrl.$request->getApiMethodName();
        }

        $getApiParas = $request->getApiParas();

        if(!empty($component_access_token) && empty($access_token)) {
            if($request->get_method_type() == 'GET') {
                $getApiParas["component_access_token"] = $component_access_token;
            } else {
                $spstr = strstr($url,"?")?"&":"?";
                $url .= $spstr.'component_access_token='.$component_access_token;
            }
        }

        if(empty($component_access_token) && !empty($access_token)) {
            if($request->get_method_type() == 'GET') {
                $getApiParas["access_token"] = $access_token;
            } else {
                $spstr = strstr($url,"?")?"&":"?";
                $url .= $spstr.'access_token='.$access_token;
            }
        }

        $client = new \GuzzleHttp\Client();
        $options = [
            'headers' => [
                'content-type' => 'application/json'
            ]
        ];

        switch ($request->get_method_type()) {
            case 'GET':
                $options["query"] = $getApiParas;
                break;
            case 'POST':
                if($getApiParas) {
                    $options['body'] = json_encode($getApiParas,JSON_UNESCAPED_UNICODE);
                } else {
                    $options['body'] = "{}";
                }
                break;
        }

        try{
            $response = $client->request($request->get_method_type(), $url,$options);
            $contentType = $response->getHeader("Content-Type")[0];
            if($contentType == 'image/jpeg') {
                return $response->getBody()->getContents();
            } else {
                return json_decode($response->getBody()->getContents());
            }
        } catch (ClientException $e){
            return ["errcode"=>-1,"errmsg"=>"服务器响应异常"];
        }
    }


    /**
     * 将公众平台回复用户的消息加密打包.
     * <ol>
     *    <li>对要发送的消息进行AES-CBC加密</li>
     *    <li>生成安全签名</li>
     *    <li>将消息密文和安全签名打包成xml格式</li>
     * </ol>
     *
     * @param $replyMsg string 公众平台待回复用户的消息，xml格式的字符串
     * @param $timeStamp string 时间戳，可以自己生成，也可以用URL参数的timestamp
     * @param $nonce string 随机串，可以自己生成，也可以用URL参数的nonce
     * @param &$encryptMsg string 加密后的可以直接回复用户的密文，包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
     *                      当return返回0时有效
     *
     * @return int 成功0，失败返回对应的错误码
     */
    public function encryptMsg($replyMsg, $timeStamp, $nonce, &$encryptMsg)
    {
        $pc = new Prpcrypt($this->EncodingAESKey);

        //加密
        $array = $pc->encrypt($replyMsg, $this->AppID);
        $ret = $array[0];
        if ($ret != 0) {
            return $ret;
        }

        $encrypt = $array[1];

        //生成安全签名
        $sha1 = new SHA1();
        $array = $sha1->getSHA1($this->Token, $timeStamp, $nonce, $encrypt);
        $ret = $array[0];
        if ($ret != 0) {
            return $ret;
        }
        $signature = $array[1];

        //生成发送的xml
        $xmlparse = new XMLParse();
        $encryptMsg = $xmlparse->generate($encrypt, $signature, $timeStamp, $nonce);
        return ErrorCode::$OK;
    }


    /**
     * 检验消息的真实性，并且获取解密后的明文.
     * <ol>
     *    <li>利用收到的密文生成安全签名，进行签名验证</li>
     *    <li>若验证通过，则提取xml中的加密消息</li>
     *    <li>对消息进行解密</li>
     * </ol>
     *
     * @param $msgSignature string 签名串，对应URL参数的msg_signature
     * @param $timestamp string 时间戳 对应URL参数的timestamp
     * @param $nonce string 随机串，对应URL参数的nonce
     * @param $postData string 密文，对应POST请求的数据
     * @param &$msg string 解密后的原文，当return返回0时有效
     *
     * @return int 成功0，失败返回对应的错误码
     */
    public function decryptMsg($msgSignature, $timestamp = null, $nonce, $postData, &$msg)
    {
        if (strlen($this->EncodingAESKey) != 43) {
            return ErrorCode::$IllegalAesKey;
        }

        $pc = new Prpcrypt($this->EncodingAESKey);

        //提取密文
        $xmlparse = new XMLParse;
        $array = $xmlparse->extract($postData);
        $ret = $array[0];

        if ($ret != 0) {
            return $ret;
        }

        if ($timestamp == null) {
            $timestamp = time();
        }

        $encrypt = $array[1];
        $touser_name = $array[2];

        //验证安全签名
        $sha1 = new SHA1;
        $array = $sha1->getSHA1($this->Token, $timestamp, $nonce, $encrypt);
        $ret = $array[0];

        if ($ret != 0) {
            return $ret;
        }

        $signature = $array[1];
        if ($signature != $msgSignature) {
            return ErrorCode::$ValidateSignatureError;
        }
        $result = $pc->decrypt($encrypt, $this->AppID);
        if ($result[0] != 0) {
            return $result[0];
        }
        $msg = $result[1];

        return ErrorCode::$OK;
    }

    /**
     * component_access_token有效期2h，
     * 当遭遇异常没有及时收到component_verify_ticket时，
     * 建议以上一次可用的component_verify_ticket继续生成component_access_token。
     * 避免出现因为 component_verify_ticket 接收失败而无法更新 component_access_token 的情况。
     * @return void
     */
    public function getComponentaccesstoken() {
        $cache = Yii::$app->cache;
        $keyname_token = "component_access_token-".$this->AppID;
        $keyname_comp = "component_verify_ticket-".$this->AppID;
        if($cache->exists($keyname_token)) {
            return $cache->get($keyname_token);
        } else {
            if($cache->exists($keyname_comp)) {
                $request = new ComponentToken();
                $request->setComponent_appid($this->AppID);
                $request->setComponent_appsecret($this->AppSecret);
                $request->setComponent_verify_ticket($cache->get($keyname_comp));
                $response = $this->execute($request);
                if(property_exists($response,'component_access_token')) {
                    $cache->set($keyname_token,$response->component_access_token,$response->expires_in);
                    return $response->component_access_token;
                }
            }
        }
    }


    /**
     * 获取accesstoken
     * @return mixed
     * @throws HttpException
     */
    public function getAccess_token() {

        $cache = Yii::$app->cache;
        $cache_key = "mpweixin-accesstoken-".$this->AppID;
        if($cache->exists($cache_key)) {
            return $cache->get($cache_key);
        }
        $request = new TokenRequest();
        $request->set_grant_type("client_credential");
        $request->set_appid($this->AppID);
        $request->set_secret($this->AppSecret);
        $response = $this->execute($request);
        if(property_exists($response,'errcode')) {
            return false;
        }
        $cache->set($cache_key,$response->access_token,$response->expires_in);
        return $response->access_token;
    }




}